/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package project2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import project1.CandidateSolution;

/**
 *
 * @author rr
 */
public class Solver {

    private Solution bestSolution;
    private int n = SetUp.getInstance().getLengthOfSchedule() * SetUp.getInstance().getNumberOfEmployees();
    private static Solver instance = null;
    private int solutionCount = 0;
    private int firstErrorCount = 0;

    private Solver() {
    }

    public static Solver getInstance() {
        if (instance == null) {
            instance = new Solver();
        }
        return instance;
    }

    public void solve() {


        int[] initAssignment = getInitAssignment();

        bestSolution = new Solution(initAssignment);

        bestSolution.randomize(10000);

        boolean searchOn = true;
        ArrayList<Solution> neighbourSolutions;
        float minError = bestSolution.getError();
        this.firstErrorCount = (int) minError;

        while (searchOn) {
            // Run.printWorkSchedule(bestSolution);
            neighbourSolutions = getNeighbourSolutions(bestSolution, minError);
            
            //choose next best solution...
            
            int minIndex = 0;

            minError=neighbourSolutions.get(0).getError();
            System.out.println("CURRENT ERROR: "+minError);
            
            if (minError < 1) {
                searchOn = false;
                Run.printWorkSchedule(neighbourSolutions.get(0));
                break;
            }

            if (minError < SetUp.getInstance().getStopLimit()) {
                //STOP CRITERION
                Run.printWorkSchedule(bestSolution);

                boolean[][] allOnes = new boolean[project1.SetUp.getInstance().getNumberOfShifts() + 1][project1.SetUp.getInstance().getNumberOfEmployees() * project1.SetUp.getInstance().getLengthOfSchedule()];

                for (int i = 0; i < allOnes.length; ++i) {
                    for (int j = 0; j < allOnes[0].length; ++j) {
                        allOnes[i][j] = true;
                    }
                }

                int[] tempAssignment = new int[n];
                for (int i = 0; i < n; i++) {
                    tempAssignment[i] = -1;
                }
                CandidateSolution firstTry = new CandidateSolution(tempAssignment, allOnes);



                int[] curAssignment = bestSolution.getOldAssignmentArray();
                Run.printWorkSchedule(curAssignment);

                int assignmentCount = 0;
                for (int i = 0; i < curAssignment.length; ++i) {
                    if (curAssignment[i] != -1) {
                        firstTry = firstTry.assignNext(curAssignment[i], i);
                        ++assignmentCount;
                    }
                }

                System.out.println("TRYING EXACT SOLVING WITH " + assignmentCount + " assigned values OF " + curAssignment.length);
                System.out.println("possible assignments: " + firstTry.getPossibleAssignmentCount());
                TraverseSolver exactSolver = new TraverseSolver();

                exactSolver.traverse(firstTry);

                if (exactSolver.getSolution() == null) {
                    //try again
                    System.out.println("over");
                    bestSolution.randomize(100000);
                    minError = bestSolution.getError();
                    this.firstErrorCount = (int) minError;
                    this.solutionCount = 0;
                    SetUp.getInstance().resetT();
                } else {
                    bestSolution = new Solution(exactSolver.getSolution().getTempAssignments());
                    Run.printWorkSchedule(bestSolution);
                    if (bestSolution.getError() > 0) {

                        System.out.println("OOOOOOOOOOOOOOOOOOO");
                        bestSolution.randomize(100000);
                        minError = bestSolution.getError();
                        this.firstErrorCount = (int) minError;
                        this.solutionCount = 0;
                        SetUp.getInstance().resetT();
                    } else {
                        return;
                    }
                }
                //try exact solution from PHASE 1


                //bestSolution.randomize(1000);
            } else {
                if (neighbourSolutions.isEmpty()) {
                    bestSolution = new Solution(initAssignment);
                    bestSolution.randomize(10000);
                    minError = bestSolution.getError();
                    this.firstErrorCount = (int) minError;
                    this.solutionCount = 0;

                    SetUp.getInstance().resetT();
                } else {
                    bestSolution = neighbourSolutions.get(minIndex);
                }
            }
            //Arrays.sort(error);






        }


    }

    private ArrayList<Solution> getNeighbourSolutions(Solution solution, float currentMinError) {
        System.out.println("SEARCHING SOLUTIONS: ");
        ArrayList<Solution> solutions = new ArrayList<Solution>();

        this.recursiveSwap(solution, SetUp.getInstance().getkOpt() - 1, solutions, currentMinError, 3);
        //this.recursiveSwap(solution, SetUp.getInstance().getkOpt() - 1, solutions, currentMinError, 2);
        //this.recursiveSwap(solution, SetUp.getInstance().getkOpt() - 1, solutions, currentMinError, 1);

        return solutions;
    }

    private boolean recursiveSwap(Solution oldSolution, int level, ArrayList<Solution> solutions, float minError, int range) {

        int k = SetUp.getInstance().getkOpt();
        Random rand=new Random();
        for(int in=0;in<SetUp.getInstance().getLengthOfSchedule()*SetUp.getInstance().getNumberOfEmployees()*SetUp.getInstance().getNumberOfEmployees();in++)/*for (int weekDayId = 0; weekDayId < SetUp.getInstance().getLengthOfSchedule(); ++weekDayId)*/ {
        int weekDayId=rand.nextInt(SetUp.getInstance().getLengthOfSchedule());    
        /*for (int employeeId = 0; employeeId < SetUp.getInstance().getNumberOfEmployees(); ++employeeId)*/ {
            int employeeId = rand.nextInt(SetUp.getInstance().getNumberOfEmployees());
                /*for (int swapEmployee = employeeId + 1; swapEmployee < SetUp.getInstance().getNumberOfEmployees(); ++swapEmployee)*/ {
                    int swapEmployee=rand.nextInt(SetUp.getInstance().getNumberOfEmployees());
                    //swap Employees
                    int[] a = oldSolution.getAssignments();

                    int[] b = new int[a.length];
                    System.arraycopy(a, 0, b, 0, a.length);

                    int i1 = weekDayId + SetUp.getInstance().getLengthOfSchedule() * swapEmployee;
                    int i2 = weekDayId + SetUp.getInstance().getLengthOfSchedule() * employeeId;
                    for (int i = 0; i < range; i++) {
                        int swap = b[wrapIndex(i1 + i)];
                        b[wrapIndex(i1 + i)] = b[wrapIndex(i2 + i)];
                        b[wrapIndex(i2 + i)] = swap;
                    }
                    Solution sol = new Solution(b);
                    if (SetUp.getInstance().decreaseTEveryTime()) {
                        SetUp.getInstance().decreaseT();
                    }
                    this.solutionCount++;
                    if (level == 0 ) {
                        //stochastic hill climbing
                        float t = SetUp.getInstance().getStochasticHillClimbingT();
                        
                        double probab;
                        if(minError>sol.getError())
                            probab=1;
                        else if(minError<sol.getError()){
                            probab= (1 / (1 + Math.exp(((-minError + sol.getError()) / t))));
                            //System.out.println("prob="+probab+"; "+"errordif="+(sol.getError()-minError)+"; T="+t);
                        }
                        else
                            probab=0.5;
                        

                        Random r = new Random();
                        double otherDouble = r.nextDouble();

                        if (probab > otherDouble) {
                            System.out.println("PROB: " + probab + " R: " + otherDouble);
                            solutions.add(sol);
                            if (!SetUp.getInstance().decreaseTEveryTime()) {
                                SetUp.getInstance().decreaseT();
                            }
                            return false;
                        }
                    }
                    if (level > 0) {

                        if(!this.recursiveSwap(sol, level - 1, solutions, minError, range))
                            return false;
                    }
                }
            }
        }


        return true;
    }

    private int[] getInitAssignment() {

        int[][] tempReqMat = SetUp.getInstance().getTemporalRequirementsMatrix();


        int[] initAssignments = new int[n];

        for (int i = 0; i < initAssignments.length; ++i) {
            initAssignments[i] = SetUp.getInstance().getNumberOfShifts();
        }

        for (int j = 0; j < SetUp.getInstance().getLengthOfSchedule(); ++j) {

            int daySum = 0;

            for (int i = 0; i < SetUp.getInstance().getNumberOfShifts(); ++i) {

                int tempReqs = tempReqMat[i][j];
                for (int k = 0; k < tempReqs; ++k) {
                    initAssignments[j + (daySum * SetUp.getInstance().getLengthOfSchedule())] = i;
                    ++daySum;
                }
            }
        }

        //randomize?




        return initAssignments;

    }

    public Solution getBestSolution() {
        return bestSolution;
    }

    public void setBestSolution(Solution bestSolution) {
        this.bestSolution = bestSolution;
    }

    private int wrapIndex(int index) {
        int n = SetUp.getInstance().getLengthOfSchedule() * SetUp.getInstance().getNumberOfEmployees();
        if (index < 0) {
            index += n;
        } else {
            index = index % n;
        }

        return index;
    }
}
